#[cfg(target_arch = "wasm32")]
extern crate instant;

#[cfg(target_arch = "wasm32")]
use instant::{Duration, Instant};
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant};

use crate::{InputCallback, Key, KeyRepeat};

pub struct KeyHandler {
    pub key_callback: Option<Box<dyn InputCallback>>,
    prev_time: Instant,
    delta_time: Duration,
    keys: [bool; 512],
    keys_prev: [bool; 512],
    keys_down_duration: [f32; 512],
    key_repeat_delay: f32,
    key_repeat_rate: f32,
}

impl KeyHandler {
    pub fn new() -> KeyHandler {
        KeyHandler {
            key_callback: None,
            keys: [false; 512],
            keys_prev: [false; 512],
            keys_down_duration: [-1.0; 512],
            prev_time: Instant::now(),
            delta_time: Duration::from_secs(0),
            key_repeat_delay: 0.250,
            key_repeat_rate: 0.050,
        }
    }

    #[inline]
    pub fn set_key_state(&mut self, key: Key, state: bool) {
        self.keys[key as usize] = state;
        if let Some(cb) = &mut self.key_callback {
            cb.set_key_state(key, state);
        }
    }

    pub fn get_keys(&self) -> Vec<Key> {
        let mut keys: Vec<Key> = Vec::new();

        for (idx, is_down) in self.keys.iter().enumerate() {
            if *is_down {
                unsafe {
                    keys.push(std::mem::transmute(idx as u8));
                }
            }
        }

        keys
    }

    pub fn update(&mut self) {
        self.delta_time = self.prev_time.elapsed();
        self.prev_time = Instant::now();
        let delta_time = self.delta_time.as_secs_f32();

        for idx in 0..self.keys.len() {
            if self.keys[idx] {
                if self.keys_down_duration[idx] < 0.0 {
                    self.keys_down_duration[idx] = 0.0;
                } else {
                    self.keys_down_duration[idx] += delta_time;
                }
            } else {
                self.keys_down_duration[idx] = -1.0;
            }
            self.keys_prev[idx] = self.keys[idx];
        }
    }

    #[inline]
    pub fn set_input_callback(&mut self, callback: Box<dyn InputCallback>) {
        self.key_callback = Some(callback);
    }

    pub fn get_keys_pressed(&self, repeat: KeyRepeat) -> Vec<Key> {
        let mut keys: Vec<Key> = Vec::new();

        for (idx, is_down) in self.keys.iter().enumerate() {
            if *is_down && self.is_key_index_pressed(idx, repeat) {
                unsafe {
                    keys.push(std::mem::transmute(idx as u8));
                }
            }
        }

        keys
    }

    pub fn get_keys_released(&self) -> Vec<Key> {
        let mut keys: Vec<Key> = Vec::new();

        for (idx, is_down) in self.keys.iter().enumerate() {
            if !(*is_down) && self.is_key_index_released(idx) {
                unsafe {
                    keys.push(std::mem::transmute(idx as u8));
                }
            }
        }

        keys
    }

    #[inline]
    pub fn is_key_down(&self, key: Key) -> bool {
        self.keys[key as usize]
    }

    #[inline]
    pub fn set_key_repeat_delay(&mut self, delay: f32) {
        self.key_repeat_delay = delay;
    }

    #[inline]
    pub fn set_key_repeat_rate(&mut self, rate: f32) {
        self.key_repeat_rate = rate;
    }

    fn is_key_index_pressed(&self, index: usize, repeat: KeyRepeat) -> bool {
        let t = self.keys_down_duration[index];

        if t == 0.0 {
            return true;
        }

        if repeat == KeyRepeat::Yes && t > self.key_repeat_delay {
            let delta_time = self.delta_time.as_secs_f32();
            let delay = self.key_repeat_delay;
            let rate = self.key_repeat_rate;
            if (((t - delay) % rate) > rate * 0.5)
                != (((t - delay - delta_time) % rate) > rate * 0.5)
            {
                return true;
            }
        }

        false
    }

    #[inline]
    pub fn is_key_pressed(&self, key: Key, repeat: KeyRepeat) -> bool {
        self.is_key_index_pressed(key as usize, repeat)
    }

    #[inline]
    pub fn is_key_released(&self, key: Key) -> bool {
        self.is_key_index_released(key as usize)
    }

    #[inline]
    fn is_key_index_released(&self, idx: usize) -> bool {
        self.keys_prev[idx] && !self.keys[idx]
    }
}
